<template>
  <view :style="{ width: width + 'px', height: height + 'px' }" class="chart-wrap">
    <canvas
      :canvas-id="canvasId"
      :style="{ width: width + 'px', height: height + 'px' }"
      @touchstart="onTouchStart"
    />
  </view>
</template>

<script>
export default {
  name: "TimeCurveChart",
  props: {
    canvasId: { type: String, default: "time-curve-canvas" },
    width: { type: Number, default: 340 }, // css px
    height: { type: Number, default: 220 }, // css px
    padding: { type: Object, default: () => ({ left: 40, right: 20, top: 20, bottom: 30 }) },
    /** data: [{time: '2025-10-01 08:00', value: 120}, ...] or time can be timestamp (ms) */
    data: { type: Array, default: () => [] },
    yMin: { type: Number, default: null }, // optional
    yMax: { type: Number, default: null }, // optional
    xTickCount: { type: Number, default: 4 },
    yTickCount: { type: Number, default: 4 },
    lineWidth: { type: Number, default: 2 },
    lineColor: { type: String, default: "#2B9AF3" }, // blue
    pointRadius: { type: Number, default: 3 },
    smooth: { type: Boolean, default: true }
  },
  data() {
    return {
      ctx: null,
      dpr: 1
    };
  },
  watch: {
    data: { handler() { this.draw(); }, deep: true },
    width() { this.setupCanvas(); },
    height() { this.setupCanvas(); }
  },
  mounted() {
    this.setupCanvas();
  },
  methods: {
setupCanvas() {
  // #ifdef MP-WEIXIN
  // ✅ 微信小程序平台使用 createCanvasContext
  const ctx = uni.createCanvasContext(this.canvasId, this);
  this.ctx = ctx;
  this.drawWithUniCtx(); // 用兼容的绘制函数
  // #endif

  // #ifndef MP-WEIXIN
  // ✅ 其他平台（App / H5）尝试获取原生 node
  const query = uni.createSelectorQuery().in(this);
  query
    .select(`#${this.canvasId}`)
    .fields({ node: true, size: true })
    .exec((res) => {
      const node = res[0] && res[0].node;
      if (!node) {
        // fallback
        const ctx2 = uni.createCanvasContext(this.canvasId, this);
        this.ctx = ctx2;
        this.drawWithUniCtx();
        return;
      }
      const dpr = (this.dpr = uni.getSystemInfoSync().pixelRatio || 1);
      node.width = this.width * dpr;
      node.height = this.height * dpr;
      const ctx = node.getContext('2d');
      ctx.scale(dpr, dpr);
      this.ctx = ctx;
      this.draw();
    });
  // #endif
},

 drawWithUniCtx() {
   const ctx = uni.createCanvasContext(this.canvasId, this);
   if (!ctx) return;
 
   const pad = this.padding;
   const left = pad.left,
     right = this.width - pad.right,
     top = pad.top,
     bottom = this.height - pad.bottom;
 
   const data = this.data || [];
   if (data.length === 0) {
     ctx.draw();
     return;
   }
 
   // ===== 数据处理 =====
   const points = data
     .map((d) => {
       const t =
         typeof d.time === "number" ? d.time : Date.parse(String(d.time));
       return { t, v: Number(d.value) };
     })
     .sort((a, b) => a.t - b.t);
 
   const tMin = points[0].t;
   const tMax = points[points.length - 1].t;
   let vMin =
     this.yMin !== null ? this.yMin : Math.min(...points.map((p) => p.v));
   let vMax =
     this.yMax !== null ? this.yMax : Math.max(...points.map((p) => p.v));
 
   if (vMin === vMax) {
     vMin -= 1;
     vMax += 1;
   }
 
   const coords = points.map((p) => {
     const x = left + ((p.t - tMin) / (tMax - tMin || 1)) * (right - left);
     const y = top + (1 - (p.v - vMin) / (vMax - vMin)) * (bottom - top);
     return { x, y, raw: p };
   });
 
   // ===== 清空画布 =====
   ctx.clearRect(0, 0, this.width, this.height);
 
   // ===== 绘制坐标轴 =====
   ctx.setStrokeStyle("#e0e0e0");
   ctx.setLineWidth(1);
   ctx.beginPath();
   ctx.moveTo(left, top);
   ctx.lineTo(left, bottom);
   ctx.lineTo(right, bottom);
   ctx.stroke();
 
   // ===== 绘制Y轴刻度 =====
   const yTickCount = this.yTickCount || 4;
   ctx.setFontSize(10);
   ctx.setFillStyle("#666");
   for (let i = 0; i <= yTickCount; i++) {
     const y = top + (i / yTickCount) * (bottom - top);
     const val = vMax - (i / yTickCount) * (vMax - vMin);
     ctx.setStrokeStyle("#f5f5f5");
     ctx.beginPath();
     ctx.moveTo(left, y);
     ctx.lineTo(right, y);
     ctx.stroke();
     ctx.fillText(val.toFixed(0), left - 25, y + 3);
   }
 
   // ===== 绘制X轴刻度 =====
   const xTickCount = this.xTickCount || 4;
   for (let i = 0; i <= xTickCount; i++) {
     const x = left + (i / xTickCount) * (right - left);
     const t = tMin + (i / xTickCount) * (tMax - tMin);
     const label = this._formatTimeLabel(t);
     ctx.setStrokeStyle("#f5f5f5");
     ctx.beginPath();
     ctx.moveTo(x, bottom);
     ctx.lineTo(x, bottom - 4);
     ctx.stroke();
     ctx.fillText(label, x - 16, bottom + 12);
   }
 
   // ===== 绘制曲线（带渐变填充）=====
   ctx.beginPath();
   ctx.moveTo(coords[0].x, coords[0].y);
   for (let i = 1; i < coords.length; i++) {
     ctx.lineTo(coords[i].x, coords[i].y);
   }
 
   // 曲线底部封闭
   ctx.lineTo(coords[coords.length - 1].x, bottom);
   ctx.lineTo(coords[0].x, bottom);
   ctx.closePath();
 
   // 创建渐变（蓝到白）
   const grad = ctx.createLinearGradient(0, top, 0, bottom);
   grad.addColorStop(0, this.lineColor);
   grad.addColorStop(1, "#ffffff");
   ctx.setFillStyle(grad);
   ctx.fill();
 
   // 绘制曲线线条
   ctx.beginPath();
   ctx.setStrokeStyle(this.lineColor);
   ctx.setLineWidth(2);
   ctx.moveTo(coords[0].x, coords[0].y);
   for (let i = 1; i < coords.length; i++) {
     ctx.lineTo(coords[i].x, coords[i].y);
   }
   ctx.stroke();
 
   // 绘制数据点
   ctx.setFillStyle(this.lineColor);
   for (const p of coords) {
     ctx.beginPath();
     ctx.arc(p.x, p.y, this.pointRadius || 3, 0, Math.PI * 2);
     ctx.fill();
   }
 
   // ===== 最后绘制 =====
   ctx.draw();
 },

    draw() {
      if (!this.ctx) return;
      // if ctx is uni canvas ctx, it may have different API; we attempted raw ctx earlier.
      // If ctx has canvas property (raw) it's native; else treat as uni ctx and fallback:
      if (!this.ctx.getImageData) {
        // uni.createCanvasContext fallback handled separately
        this.drawWithUniCtx();
        return;
      }
      const ctx = this.ctx;
      const w = this.width, h = this.height;
      // clear
      ctx.clearRect(0, 0, w, h);

      const pad = this.padding;
      const plotLeft = pad.left;
      const plotRight = w - pad.right;
      const plotTop = pad.top;
      const plotBottom = h - pad.bottom;
      const plotW = plotRight - plotLeft;
      const plotH = plotBottom - plotTop;

      // prepare data (convert time -> numeric)
      if (!this.data || this.data.length === 0) {
        // draw empty axes
        this._drawAxes(ctx, plotLeft, plotTop, plotRight, plotBottom);
        return;
      }

      // sort by time
      const points = this.data.map(d => {
        const t = (typeof d.time === 'number') ? d.time : Date.parse(String(d.time));
        return { t, v: Number(d.value) };
      }).sort((a,b)=> a.t - b.t);

      // compute min/max for x and y
      const tMin = points[0].t;
      const tMax = points[points.length - 1].t;
      let vMin = this.yMin !== null ? this.yMin : Math.min(...points.map(p=>p.v));
      let vMax = this.yMax !== null ? this.yMax : Math.max(...points.map(p=>p.v));
      if (vMin === vMax) { // avoid zero range
        vMin = vMin - 1;
        vMax = vMax + 1;
      }
      // nice padding for y
      const yPad = (vMax - vMin) * 0.08;
      vMax += yPad;
      vMin -= yPad;

      // transform to coordinates
      const coords = points.map(p => {
        const x = plotLeft + ((p.t - tMin) / (tMax - tMin || 1)) * plotW;
        const y = plotTop + (1 - (p.v - vMin) / (vMax - vMin)) * plotH;
        return { x, y, raw: p };
      });

      // draw grid & axes & ticks
      this._drawGridAndAxes(ctx, plotLeft, plotTop, plotRight, plotBottom, tMin, tMax, vMin, vMax);

      // draw smooth curve
      if (coords.length >= 2) {
        if (this.smooth && coords.length >= 3) {
          this._drawSmoothCurveWithGradient(ctx, coords, plotBottom);
        } else {
          this._drawStraightCurveWithGradient(ctx, coords, plotBottom);
        }
      }

      // draw points
      ctx.save();
      ctx.fillStyle = this.lineColor;
      for (const p of coords) {
        ctx.beginPath();
        ctx.arc(p.x, p.y, this.pointRadius, 0, Math.PI * 2);
        ctx.fill();
      }
      ctx.restore();
    },

    _drawAxes(ctx, left, top, right, bottom) {
      ctx.save();
      ctx.strokeStyle = "#e6e6e6";
      ctx.lineWidth = 1;
      // x axis
      ctx.beginPath();
      ctx.moveTo(left, bottom);
      ctx.lineTo(right, bottom);
      ctx.stroke();
      // y axis
      ctx.beginPath();
      ctx.moveTo(left, top);
      ctx.lineTo(left, bottom);
      ctx.stroke();
      ctx.restore();
    },

    _drawGridAndAxes(ctx, left, top, right, bottom, tMin, tMax, vMin, vMax) {
      const xCount = Math.max(1, this.xTickCount);
      const yCount = Math.max(1, this.yTickCount);

      ctx.save();
      ctx.font = "11px Arial";
      ctx.fillStyle = "#666";
      ctx.textAlign = "center";
      ctx.textBaseline = "top";

      // vertical grid (x ticks)
      for (let i = 0; i <= xCount; i++) {
        const x = left + (i / xCount) * (right - left);
        ctx.beginPath();
        ctx.strokeStyle = "#f0f0f0";
        ctx.lineWidth = 1;
        ctx.moveTo(x, top);
        ctx.lineTo(x, bottom);
        ctx.stroke();

        // label
        const tVal = tMin + (i / xCount) * (tMax - tMin);
        const lbl = this._formatTimeLabel(tVal);
        ctx.fillStyle = "#666";
        ctx.fillText(lbl, x, bottom + 4);
      }

      // horizontal grid (y ticks)
      ctx.textAlign = "right";
      ctx.textBaseline = "middle";
      for (let j = 0; j <= yCount; j++) {
        const y = top + (j / yCount) * (bottom - top);
        ctx.beginPath();
        ctx.strokeStyle = "#f5f5f5";
        ctx.lineWidth = 1;
        ctx.moveTo(left, y);
        ctx.lineTo(right, y);
        ctx.stroke();

        const vVal = vMax - (j / yCount) * (vMax - vMin);
        const lbl = Number(vVal.toFixed(2));
        ctx.fillStyle = "#666";
        ctx.fillText(String(lbl), left - 6, y);
      }

      // axes lines
      ctx.beginPath();
      ctx.strokeStyle = "#dcdcdc";
      ctx.lineWidth = 1;
      ctx.moveTo(left, top);
      ctx.lineTo(left, bottom);
      ctx.lineTo(right, bottom);
      ctx.stroke();

      ctx.restore();
    },

    _formatTimeLabel(t) {
      const d = new Date(t);
      // show hour if same day else show month-day
      const now = new Date();
      if (d.getFullYear() === now.getFullYear() &&
          d.getMonth() === now.getMonth() &&
          d.getDate() === now.getDate()) {
        return `${d.getHours()}:${String(d.getMinutes()).padStart(2,'0')}`;
      }
      return `${d.getMonth()+1}/${d.getDate()}`;
    },

    _drawStraightCurveWithGradient(ctx, coords, plotBottom) {
      // path of line
      ctx.save();
      ctx.lineJoin = "round";
      ctx.lineCap = "round";
      ctx.strokeStyle = this.lineColor;
      ctx.lineWidth = this.lineWidth;
      ctx.beginPath();
      ctx.moveTo(coords[0].x, coords[0].y);
      for (let i = 1; i < coords.length; i++) ctx.lineTo(coords[i].x, coords[i].y);
      ctx.stroke();

      // gradient fill
      ctx.beginPath();
      ctx.moveTo(coords[0].x, coords[0].y);
      for (let i = 1; i < coords.length; i++) ctx.lineTo(coords[i].x, coords[i].y);
      ctx.lineTo(coords[coords.length - 1].x, plotBottom);
      ctx.lineTo(coords[0].x, plotBottom);
      ctx.closePath();

      const grad = ctx.createLinearGradient(0, coords[0].y, 0, plotBottom);
      grad.addColorStop(0, this._hexToRgba(this.lineColor, 0.45));
      grad.addColorStop(1, this._hexToRgba("#ffffff", 0.0));
      ctx.fillStyle = grad;
      ctx.fill();

      ctx.restore();
    },

    _drawSmoothCurveWithGradient(ctx, coords, plotBottom) {
      ctx.save();
      ctx.lineJoin = "round";
      ctx.lineCap = "round";
      ctx.strokeStyle = this.lineColor;
      ctx.lineWidth = this.lineWidth;
      // build bezier path from Catmull-Rom
      const beziers = this._catmullRom2bezier(coords.map(p=>({x:p.x,y:p.y})));
      // stroke path
      ctx.beginPath();
      ctx.moveTo(coords[0].x, coords[0].y);
      for (const seg of beziers) {
        ctx.bezierCurveTo(seg.c1.x, seg.c1.y, seg.c2.x, seg.c2.y, seg.p.x, seg.p.y);
      }
      ctx.stroke();

      // fill path: rebuild path then close to bottom
      ctx.beginPath();
      ctx.moveTo(coords[0].x, coords[0].y);
      for (const seg of beziers) {
        ctx.bezierCurveTo(seg.c1.x, seg.c1.y, seg.c2.x, seg.c2.y, seg.p.x, seg.p.y);
      }
      ctx.lineTo(coords[coords.length - 1].x, plotBottom);
      ctx.lineTo(coords[0].x, plotBottom);
      ctx.closePath();

      const grad = ctx.createLinearGradient(0, coords[0].y, 0, plotBottom);
      grad.addColorStop(0, this._hexToRgba(this.lineColor, 0.45));
      grad.addColorStop(1, this._hexToRgba("#ffffff", 0.0));
      ctx.fillStyle = grad;
      ctx.fill();

      // subtle shadow under line
      ctx.beginPath();
      ctx.moveTo(coords[0].x, coords[0].y);
      for (const seg of beziers) {
        ctx.bezierCurveTo(seg.c1.x, seg.c1.y, seg.c2.x, seg.c2.y, seg.p.x, seg.p.y);
      }
      ctx.strokeStyle = this._hexToRgba(this.lineColor, 0.15);
      ctx.lineWidth = this.lineWidth + 6;
      ctx.stroke();

      ctx.restore();
    },

    // convert Catmull-Rom points to cubic bezier segments
    _catmullRom2bezier(points) {
      // points: [{x,y}, ...]
      const beziers = [];
      for (let i = 0; i < points.length - 1; i++) {
        const p0 = points[i - 1] || points[i];
        const p1 = points[i];
        const p2 = points[i + 1];
        const p3 = points[i + 2] || p2;
        // tension standard 0.5
        const t = 0.5;
        const c1 = {
          x: p1.x + (p2.x - p0.x) * t / 3,
          y: p1.y + (p2.y - p0.y) * t / 3
        };
        const c2 = {
          x: p2.x - (p3.x - p1.x) * t / 3,
          y: p2.y - (p3.y - p1.y) * t / 3
        };
        beziers.push({ c1, c2, p: p2 });
      }
      return beziers;
    },

    _hexToRgba(hex, alpha) {
      // accept #RRGGBB or #RGB
      let h = hex.replace('#','');
      if (h.length === 3) {
        h = h.split('').map(ch => ch+ch).join('');
      }
      const bigint = parseInt(h, 16);
      const r = (bigint >> 16) & 255;
      const g = (bigint >> 8) & 255;
      const b = bigint & 255;
      return `rgba(${r},${g},${b},${alpha})`;
    },

    // optional: basic touch handler to display tooltip later
    onTouchStart(e) {
      // placeholder — can compute nearest point and emit an event
      // this.$emit('pointTap', { ... })
    },

    // helper used by uni fallback
    _computeCoords(data, w, h) {
      if (!data || data.length === 0) return [];
      const pad = this.padding;
      const left = pad.left, right = w - pad.right, top = pad.top, bottom = h - pad.bottom;
      const tArr = data.map(d => (typeof d.time === 'number') ? d.time : Date.parse(String(d.time))).sort((a,b)=>a-b);
      const vArr = data.map(d => Number(d.value));
      const tMin = tArr[0], tMax = tArr[tArr.length-1];
      let vMin = this.yMin !== null ? this.yMin : Math.min(...vArr);
      let vMax = this.yMax !== null ? this.yMax : Math.max(...vArr);
      if (vMin === vMax) { vMin -= 1; vMax += 1; }
      const coords = data.map(d => {
        const t = (typeof d.time === 'number') ? d.time : Date.parse(String(d.time));
        const x = left + ((t - tMin) / (tMax - tMin || 1)) * (right - left);
        const y = top + (1 - (Number(d.value) - vMin) / (vMax - vMin)) * (bottom - top);
        return {x,y};
      });
      return coords;
    }
  }
};
</script>

<style scoped>
.chart-wrap {
  position: relative;
}
</style>
